/*-
 * Copyright (c) 2004, 2005, 2006 Lev Walkin <vlm@lionet.info>.
 * All rights reserved.
 * Redistribution and modifications are permitted subject to BSD license.
 */
/*
 * Read the NativeInteger.h for the explanation wrt. differences between
 * INTEGER and NativeInteger.
 * Basically, both are decoders and encoders of ASN.1 INTEGER type, but this
 * implementation deals with the standard (machine-specific) representation
 * of them instead of using the platform-independent buffer.
 */
#include <NativeInteger.h>
#include <asn_internal.h>

/*
 * NativeInteger basic type description.
 */
static const ber_tlv_tag_t asn_DEF_NativeInteger_tags[] = {
    (ASN_TAG_CLASS_UNIVERSAL | (2 << 2))};
asn_TYPE_operation_t asn_OP_NativeInteger = {
    NativeInteger_free,
    NativeInteger_print,
    NativeInteger_compare,
    NativeInteger_decode_ber,
    NativeInteger_encode_der,
    NativeInteger_decode_xer,
    NativeInteger_encode_xer,
#ifdef ASN_DISABLE_OER_SUPPORT
    0,
    0,
#else
    NativeInteger_decode_oer,  /* OER decoder */
    NativeInteger_encode_oer,  /* Canonical OER encoder */
#endif /* ASN_DISABLE_OER_SUPPORT */
#ifdef ASN_DISABLE_PER_SUPPORT
    0,
    0,
    0,
    0,
#else
    NativeInteger_decode_uper, /* Unaligned PER decoder */
    NativeInteger_encode_uper, /* Unaligned PER encoder */
    NativeInteger_decode_aper, /* Aligned PER decoder */
    NativeInteger_encode_aper, /* Aligned PER encoder */
#endif /* ASN_DISABLE_PER_SUPPORT */
    NativeInteger_random_fill,
    0 /* Use generic outmost tag fetcher */
};
asn_TYPE_descriptor_t asn_DEF_NativeInteger = {
    "INTEGER", /* The ASN.1 type is still INTEGER */
    "INTEGER",
    &asn_OP_NativeInteger,
    asn_DEF_NativeInteger_tags,
    sizeof(asn_DEF_NativeInteger_tags) / sizeof(asn_DEF_NativeInteger_tags[0]),
    asn_DEF_NativeInteger_tags, /* Same as above */
    sizeof(asn_DEF_NativeInteger_tags) / sizeof(asn_DEF_NativeInteger_tags[0]),
    {0, 0, asn_generic_no_constraint},
    0,
    0, /* No members */
    0  /* No specifics */
};

/*
 * Decode INTEGER type.
 */
asn_dec_rval_t NativeInteger_decode_ber(
    const asn_codec_ctx_t* opt_codec_ctx, const asn_TYPE_descriptor_t* td,
    void** nint_ptr, const void* buf_ptr, size_t size, int tag_mode) {
  const asn_INTEGER_specifics_t* specs =
      (const asn_INTEGER_specifics_t*) td->specifics;
  long* native = (long*) *nint_ptr;
  asn_dec_rval_t rval;
  ber_tlv_len_t length;

  /*
   * If the structure is not there, allocate it.
   */
  if (native == NULL) {
    native = (long*) (*nint_ptr = CALLOC(1, sizeof(*native)));
    if (native == NULL) {
      rval.code     = RC_FAIL;
      rval.consumed = 0;
      return rval;
    }
  }

  ASN_DEBUG("Decoding %s as INTEGER (tm=%d)", td->name, tag_mode);

  /*
   * Check tags.
   */
  rval = ber_check_tags(
      opt_codec_ctx, td, 0, buf_ptr, size, tag_mode, 0, &length, 0);
  if (rval.code != RC_OK) return rval;

  ASN_DEBUG("%s length is %d bytes", td->name, (int) length);

  /*
   * Make sure we have this length.
   */
  buf_ptr = ((const char*) buf_ptr) + rval.consumed;
  size -= rval.consumed;
  if (length > (ber_tlv_len_t) size) {
    rval.code     = RC_WMORE;
    rval.consumed = 0;
    return rval;
  }

  /*
   * ASN.1 encoded INTEGER: buf_ptr, length
   * Fill the native, at the same time checking for overflow.
   * If overflow occured, return with RC_FAIL.
   */
  {
    INTEGER_t tmp;
    union {
      const void* constbuf;
      void* nonconstbuf;
    } unconst_buf;
    long l;

    unconst_buf.constbuf = buf_ptr;
    tmp.buf              = (uint8_t*) unconst_buf.nonconstbuf;
    tmp.size             = length;

    if ((specs && specs->field_unsigned) ?
            asn_INTEGER2ulong(&tmp, (unsigned long*) &l) /* sic */
            :
            asn_INTEGER2long(&tmp, &l)) {
      rval.code     = RC_FAIL;
      rval.consumed = 0;
      return rval;
    }

    *native = l;
  }

  rval.code = RC_OK;
  rval.consumed += length;

  ASN_DEBUG(
      "Took %ld/%ld bytes to encode %s (%ld)", (long) rval.consumed,
      (long) length, td->name, (long) *native);

  return rval;
}

/*
 * Encode the NativeInteger using the standard INTEGER type DER encoder.
 */
asn_enc_rval_t NativeInteger_encode_der(
    const asn_TYPE_descriptor_t* sd, const void* ptr, int tag_mode,
    ber_tlv_tag_t tag, asn_app_consume_bytes_f* cb, void* app_key) {
  unsigned long native = *(const unsigned long*) ptr; /* Disable sign ext. */
  asn_enc_rval_t erval;
  INTEGER_t tmp;

#ifdef WORDS_BIGENDIAN /* Opportunistic optimization */

  tmp.buf  = (uint8_t*) &native;
  tmp.size = sizeof(native);

#else  /* Works even if WORDS_BIGENDIAN is not set where should've been */
  uint8_t buf[sizeof(native)];
  uint8_t* p;

  /* Prepare a fake INTEGER */
  for (p = buf + sizeof(buf) - 1; p >= buf; p--, native >>= 8)
    *p = (uint8_t) native;

  tmp.buf  = buf;
  tmp.size = sizeof(buf);
#endif /* WORDS_BIGENDIAN */

  /* Encode fake INTEGER */
  erval = INTEGER_encode_der(sd, &tmp, tag_mode, tag, cb, app_key);
  if (erval.structure_ptr == &tmp) {
    erval.structure_ptr = ptr;
  }
  return erval;
}

/*
 * Decode the chunk of XML text encoding INTEGER.
 */
asn_dec_rval_t NativeInteger_decode_xer(
    const asn_codec_ctx_t* opt_codec_ctx, const asn_TYPE_descriptor_t* td,
    void** sptr, const char* opt_mname, const void* buf_ptr, size_t size) {
  const asn_INTEGER_specifics_t* specs =
      (const asn_INTEGER_specifics_t*) td->specifics;
  asn_dec_rval_t rval;
  INTEGER_t st;
  void* st_ptr = (void*) &st;
  long* native = (long*) *sptr;

  if (!native) {
    native = (long*) (*sptr = CALLOC(1, sizeof(*native)));
    if (!native) ASN__DECODE_FAILED;
  }

  memset(&st, 0, sizeof(st));
  rval =
      INTEGER_decode_xer(opt_codec_ctx, td, &st_ptr, opt_mname, buf_ptr, size);
  if (rval.code == RC_OK) {
    long l;
    if ((specs && specs->field_unsigned) ?
            asn_INTEGER2ulong(&st, (unsigned long*) &l) /* sic */
            :
            asn_INTEGER2long(&st, &l)) {
      rval.code     = RC_FAIL;
      rval.consumed = 0;
    } else {
      *native = l;
    }
  } else {
    /*
     * Cannot restart from the middle;
     * there is no place to save state in the native type.
     * Request a continuation from the very beginning.
     */
    rval.consumed = 0;
  }
  ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_INTEGER, &st);
  return rval;
}

asn_enc_rval_t NativeInteger_encode_xer(
    const asn_TYPE_descriptor_t* td, const void* sptr, int ilevel,
    enum xer_encoder_flags_e flags, asn_app_consume_bytes_f* cb,
    void* app_key) {
  const asn_INTEGER_specifics_t* specs =
      (const asn_INTEGER_specifics_t*) td->specifics;
  char scratch[32]; /* Enough for 64-bit int */
  asn_enc_rval_t er;
  const long* native = (const long*) sptr;

  (void) ilevel;
  (void) flags;

  if (!native) ASN__ENCODE_FAILED;

  er.encoded = snprintf(
      scratch, sizeof(scratch),
      (specs && specs->field_unsigned) ? "%lu" : "%ld", *native);
  if (er.encoded <= 0 || (size_t) er.encoded >= sizeof(scratch) ||
      cb(scratch, er.encoded, app_key) < 0)
    ASN__ENCODE_FAILED;

  ASN__ENCODED_OK(er);
}

#ifndef ASN_DISABLE_PER_SUPPORT

asn_dec_rval_t NativeInteger_decode_uper(
    const asn_codec_ctx_t* opt_codec_ctx, const asn_TYPE_descriptor_t* td,
    const asn_per_constraints_t* constraints, void** sptr, asn_per_data_t* pd) {
  const asn_INTEGER_specifics_t* specs =
      (const asn_INTEGER_specifics_t*) td->specifics;
  asn_dec_rval_t rval;
  long* native = (long*) *sptr;
  INTEGER_t tmpint;
  void* tmpintptr = &tmpint;

  (void) opt_codec_ctx;
  ASN_DEBUG("Decoding NativeInteger %s (UPER)", td->name);

  if (!native) {
    native = (long*) (*sptr = CALLOC(1, sizeof(*native)));
    if (!native) ASN__DECODE_FAILED;
  }

  memset(&tmpint, 0, sizeof tmpint);
  rval = INTEGER_decode_uper(opt_codec_ctx, td, constraints, &tmpintptr, pd);
  if (rval.code == RC_OK) {
    if ((specs && specs->field_unsigned) ?
            asn_INTEGER2ulong(&tmpint, (unsigned long*) native) :
            asn_INTEGER2long(&tmpint, native))
      rval.code = RC_FAIL;
    else
      ASN_DEBUG("NativeInteger %s got value %ld", td->name, *native);
  }
  ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_INTEGER, &tmpint);

  return rval;
}

asn_enc_rval_t NativeInteger_encode_uper(
    const asn_TYPE_descriptor_t* td, const asn_per_constraints_t* constraints,
    const void* sptr, asn_per_outp_t* po) {
  const asn_INTEGER_specifics_t* specs =
      (const asn_INTEGER_specifics_t*) td->specifics;
  asn_enc_rval_t er;
  long native;
  INTEGER_t tmpint;

  if (!sptr) ASN__ENCODE_FAILED;

  native = *(const long*) sptr;

  ASN_DEBUG("Encoding NativeInteger %s %ld (UPER)", td->name, native);

  memset(&tmpint, 0, sizeof(tmpint));
  if ((specs && specs->field_unsigned) ? asn_ulong2INTEGER(&tmpint, native) :
                                         asn_long2INTEGER(&tmpint, native))
    ASN__ENCODE_FAILED;
  er = INTEGER_encode_uper(td, constraints, &tmpint, po);
  ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_INTEGER, &tmpint);
  return er;
}

asn_dec_rval_t NativeInteger_decode_aper(
    const asn_codec_ctx_t* opt_codec_ctx, const asn_TYPE_descriptor_t* td,
    const asn_per_constraints_t* constraints, void** sptr, asn_per_data_t* pd) {
  const asn_INTEGER_specifics_t* specs =
      (const asn_INTEGER_specifics_t*) td->specifics;
  asn_dec_rval_t rval;
  long* native = (long*) *sptr;
  INTEGER_t tmpint;
  void* tmpintptr = &tmpint;

  (void) opt_codec_ctx;
  ASN_DEBUG("Decoding NativeInteger %s (APER)", td->name);
  // printf("test0515 Decoding NativeInteger %s (APER)\n", td->name);

  if (!native) {
    native = (long*) (*sptr = CALLOC(1, sizeof(*native)));
    if (!native) ASN__DECODE_FAILED;
  }

  memset(&tmpint, 0, sizeof tmpint);
  rval = INTEGER_decode_aper(opt_codec_ctx, td, constraints, &tmpintptr, pd);
  if (rval.code == RC_OK) {
    if ((specs && specs->field_unsigned) ?
            asn_INTEGER2ulong(&tmpint, (unsigned long*) native) :
            asn_INTEGER2long(&tmpint, native))
      rval.code = RC_FAIL;
    else {
      ASN_DEBUG("NativeInteger %s got value %ld", td->name, *native);
      // printf("test0515 NativeInteger %s got value %ld\n", td->name, *native);
    }
  }
  ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_INTEGER, &tmpint);

  return rval;
}

asn_enc_rval_t NativeInteger_encode_aper(
    const asn_TYPE_descriptor_t* td, const asn_per_constraints_t* constraints,
    const void* sptr, asn_per_outp_t* po) {
  const asn_INTEGER_specifics_t* specs =
      (const asn_INTEGER_specifics_t*) td->specifics;
  asn_enc_rval_t er;
  long native;
  INTEGER_t tmpint;

  if (!sptr) ASN__ENCODE_FAILED;

  native = *(const long*) sptr;

  ASN_DEBUG("Encoding NativeInteger %s %ld (APER)", td->name, native);

  memset(&tmpint, 0, sizeof(tmpint));
  if ((specs && specs->field_unsigned) ?
          asn_ulong2INTEGER(&tmpint, (unsigned long) native) :
          asn_long2INTEGER(&tmpint, native))
    ASN__ENCODE_FAILED;
  er = INTEGER_encode_aper(td, constraints, &tmpint, po);
  ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_INTEGER, &tmpint);
  return er;
}

#endif /* ASN_DISABLE_PER_SUPPORT */

/*
 * INTEGER specific human-readable output.
 */
int NativeInteger_print(
    const asn_TYPE_descriptor_t* td, const void* sptr, int ilevel,
    asn_app_consume_bytes_f* cb, void* app_key) {
  const asn_INTEGER_specifics_t* specs =
      (const asn_INTEGER_specifics_t*) td->specifics;
  const long* native = (const long*) sptr;
  char scratch[32]; /* Enough for 64-bit int */
  int ret;

  (void) td;     /* Unused argument */
  (void) ilevel; /* Unused argument */

  if (native) {
    long value = *native;
    ret        = snprintf(
        scratch, sizeof(scratch),
        (specs && specs->field_unsigned) ? "%lu" : "%ld", value);
    assert(ret > 0 && (size_t) ret < sizeof(scratch));
    if (cb(scratch, ret, app_key) < 0) return -1;
    if (specs && (value >= 0 || !specs->field_unsigned)) {
      const asn_INTEGER_enum_map_t* el = INTEGER_map_value2enum(specs, value);
      if (el) {
        if (cb(" (", 2, app_key) < 0) return -1;
        if (cb(el->enum_name, el->enum_len, app_key) < 0) return -1;
        if (cb(")", 1, app_key) < 0) return -1;
      }
    }
    return 0;
  } else {
    return (cb("<absent>", 8, app_key) < 0) ? -1 : 0;
  }
}

void NativeInteger_free(
    const asn_TYPE_descriptor_t* td, void* ptr,
    enum asn_struct_free_method method) {
  if (!td || !ptr) return;

  ASN_DEBUG("Freeing %s as INTEGER (%d, %p, Native)", td->name, method, ptr);

  switch (method) {
    case ASFM_FREE_EVERYTHING:
      FREEMEM(ptr);
      break;
    case ASFM_FREE_UNDERLYING:
      break;
    case ASFM_FREE_UNDERLYING_AND_RESET:
      memset(ptr, 0, sizeof(long));
      break;
  }
}

int NativeInteger_compare(
    const asn_TYPE_descriptor_t* td, const void* aptr, const void* bptr) {
  (void) td;

  if (aptr && bptr) {
    const asn_INTEGER_specifics_t* specs =
        (const asn_INTEGER_specifics_t*) td->specifics;
    if (specs && specs->field_unsigned) {
      const unsigned long* a = aptr;
      const unsigned long* b = bptr;
      if (*a < *b) {
        return -1;
      } else if (*a > *b) {
        return 1;
      } else {
        return 0;
      }
    } else {
      const long* a = aptr;
      const long* b = bptr;
      if (*a < *b) {
        return -1;
      } else if (*a > *b) {
        return 1;
      } else {
        return 0;
      }
    }
  } else if (!aptr) {
    return -1;
  } else {
    return 1;
  }
}

asn_random_fill_result_t NativeInteger_random_fill(
    const asn_TYPE_descriptor_t* td, void** sptr,
    const asn_encoding_constraints_t* constraints, size_t max_length) {
  const asn_INTEGER_specifics_t* specs =
      (const asn_INTEGER_specifics_t*) td->specifics;
  asn_random_fill_result_t result_ok      = {ARFILL_OK, 1};
  asn_random_fill_result_t result_failed  = {ARFILL_FAILED, 0};
  asn_random_fill_result_t result_skipped = {ARFILL_SKIPPED, 0};
  long* st                                = *sptr;
  const asn_INTEGER_enum_map_t* emap;
  size_t emap_len;
  intmax_t value;
  int find_inside_map;

  if (max_length == 0) return result_skipped;

  if (st == NULL) {
    st = (long*) CALLOC(1, sizeof(*st));
    if (st == NULL) {
      return result_failed;
    }
  }

  if (specs) {
    emap     = specs->value2enum;
    emap_len = specs->map_count;
    if (specs->strict_enumeration) {
      find_inside_map = emap_len > 0;
    } else {
      find_inside_map = emap_len ? asn_random_between(0, 1) : 0;
    }
  } else {
    emap            = 0;
    emap_len        = 0;
    find_inside_map = 0;
  }

  if (find_inside_map) {
    assert(emap_len > 0);
    value = emap[asn_random_between(0, emap_len - 1)].nat_value;
  } else {
    const asn_per_constraints_t* ct;

    static const long variants[] = {
        -65536, -65535, -65534, -32769, -32768, -32767, -16385, -16384,
        -16383, -257,   -256,   -255,   -254,   -129,   -128,   -127,
        -126,   -1,     0,      1,      126,    127,    128,    129,
        254,    255,    256,    257,    16383,  16384,  16385,  32767,
        32768,  32769,  65534,  65535,  65536,  65537};
    if (specs && specs->field_unsigned) {
      assert(variants[18] == 0);
      value = variants[asn_random_between(
          18, sizeof(variants) / sizeof(variants[0]) - 1)];
    } else {
      value = variants[asn_random_between(
          0, sizeof(variants) / sizeof(variants[0]) - 1)];
    }

    if (!constraints) constraints = &td->encoding_constraints;
    ct = constraints ? constraints->per_constraints : 0;
    if (ct && (ct->value.flags & APC_CONSTRAINED)) {
      if (value < ct->value.lower_bound || value > ct->value.upper_bound) {
        value =
            asn_random_between(ct->value.lower_bound, ct->value.upper_bound);
      }
    }
  }

  *sptr = st;
  *st   = value;
  return result_ok;
}
